#include "PPMC.h"
#include "../Aritmetico/Aritmetico.h"

PPMC *PPMC::instance=NULL;

PPMC::PPMC(Contexto::Orden orden,ICompresor *compresorAritmetico,
	Modelo *modeloCero):
    modeloMenosUno(),
    contexto(orden,modeloCero),
    compresorAritmetico(compresorAritmetico)
{
	if (this->compresorAritmetico==NULL)
		this->compresorAritmetico=new Aritmetico();
}

PPMC::~PPMC()
{
	delete this->compresorAritmetico;
}

/**
* Comprime el simbolo pasado.
*/
void PPMC::comprimirSimbolo(const Simbolo simbolo)
{
    bool seguir, encontrado;
    FrecuenciasSimbolos simbolosExcluir;
    Contexto::ListadoModelos listadoModelosActualizar;
    Modelo *modelo;
    
    modelo=this->contexto.getModeloMayorOrden();
    seguir=(modelo!=NULL);
    encontrado=false;
    
    while (seguir)
    {
        const FrecuenciasSimbolos &frecuenciasSimbolos=
            modelo->getFrecuenciasSimbolos();
        
		// Verifico si el simbolo se encuentra dentro del modelo
		encontrado=modelo->contiene(simbolo);

		// En caso de no estar en el modelo...
		if (!encontrado)
		{
			// ...se comprime un Esc pasando la frecuencia de simbolos y los
			// simbolos a excluir
			this->compresorAritmetico->comprimir(SimboloESC,
				frecuenciasSimbolos,simbolosExcluir,*(this->salida));

			// Se agregan los simbolos del modelo a los simbolos a excluir.
			// No agrega el Esc.
			simbolosExcluir.addSimbolos(frecuenciasSimbolos);

		}
		else
		{
			// ...se comprime el simbolo pasando la frecuencia de simbolos y los
			// simbolos a excluir
			this->compresorAritmetico->comprimir(simbolo,frecuenciasSimbolos,
				simbolosExcluir,*(this->salida));
		}
        
        // Se agrega el modelo para ser actualizado (se actualizan los modelos
        // en los que no se encontró el simbolo y el modelo en el que se encontró,
        // los modelos en los que no se buscó no son actualizados).
        // Se actualiza después de comprimir y no ahora porque sino no se va a poder
        // descomprimir.
        listadoModelosActualizar.push_back(modelo);
        
        // Pasamos al modelo de un orden menor.
        modelo=this->contexto.getModeloAnteriorOrden();

        // En caso que se haya encontrado el simbolo o que hayamos llegado
        // al final del listado (en reversa) terminamos el ciclo
        seguir=((modelo!=NULL) && (!encontrado));
    }
    
    if (!encontrado)
    {
        const FrecuenciasSimbolos &frecuenciasSimbolos=
            ((Modelo &)this->modeloMenosUno).getFrecuenciasSimbolos();

        this->compresorAritmetico->comprimirEquiprovable(simbolo,
            frecuenciasSimbolos,*(this->salida));
            
        ((Modelo &)this->modeloMenosUno).removeSimbolo(simbolo);
    }

    Contexto::ListadoModelos::iterator itModeloAct=
		listadoModelosActualizar.begin();

    // Se recorren los modelos y se actualizan con el simbolo encontrado.
    while (itModeloAct!=listadoModelosActualizar.end())
    {
        // Se actualiza la frecuencia o se agrega el simbolo en las
        // frecuencias de simbolos del modelo (también actualiza el ESC en
        // caso de agregar)
        (*itModeloAct)->actualizarFrecuencia(simbolo);

        itModeloAct++;
    }

    // Se actualiza el contexto
    contexto.addSimbolo(simbolo);

}

Simbolo PPMC::descomprimirSimbolo()
{
    bool seguir, encontrado;
    Simbolo simbolo;
    FrecuenciasSimbolos simbolosExcluir;
    Contexto::ListadoModelos listadoModelosActualizar;
    Modelo *modelo;

    modelo=this->contexto.getModeloMayorOrden();
    
    seguir=(modelo!=NULL);
    encontrado=false;
    
    while (seguir)
    {
        // Se agrega el modelo para ser actualizado (se actualizan los modelos
        // en los que no se encontró el simbolo y el modelo en el que se encontró,
        // los modelos en los que no se buscó no son actualizados).
        listadoModelosActualizar.push_back(modelo);

        const FrecuenciasSimbolos &frecuenciasSimbolos=modelo->getFrecuenciasSimbolos();
        
        // Obtengo el simbolo que corresponde al listadode frecuencias.
        simbolo=this->compresorAritmetico->descomprimir(
                frecuenciasSimbolos,simbolosExcluir,*(this->entrada));

        encontrado=(simbolo!=SimboloESC);

        // En caso de no ser un ESC...
        if (!encontrado)
        {
			// Se agregan los simbolos del modelo a los simbolos a excluir.
			// No agrega el Esc.
			simbolosExcluir.addSimbolos(frecuenciasSimbolos);

			// Pasamos al modelo de un orden menor.
	        modelo=this->contexto.getModeloAnteriorOrden();
        }

        // En caso que se haya encontrado el simbolo o que hayamos llegado
        // al final del listado (en reversa) terminamos el ciclo
        seguir=(!encontrado)&&(modelo!=NULL);
    }
    
    if (!encontrado)
    {
        const FrecuenciasSimbolos &frecuenciasSimbolos=
        		((Modelo &)this->modeloMenosUno).getFrecuenciasSimbolos();

        simbolo=this->compresorAritmetico->descomprimirEquiprovable(
            frecuenciasSimbolos,*(this->entrada));
            
        ((Modelo &)this->modeloMenosUno).removeSimbolo(simbolo);
    }
    
    Contexto::ListadoModelos::iterator itModeloAct=
		listadoModelosActualizar.begin();

    // Se recorren los modelos y se actualizan con el simbolo encontrado.    
    while (itModeloAct!=listadoModelosActualizar.end())
    {
        // Se actualiza la frecuencia o se agrega el simbolo en las 
        // frecuencias de simbolos del modelo (también actualiza el ESC en 
        // caso de agregar)
        (*itModeloAct)->actualizarFrecuencia(simbolo);

        itModeloAct++;
    }
    
    // Se actualiza el contexto
    contexto.addSimbolo(simbolo);
    
    return simbolo;
}

void PPMC::inicializarEntradaSalida(std::istream &entrada,std::ostream &salida)
{
    entrada.rdbuf()->pubsetbuf(this->bufferEntrada,PPMC_BUFFER_SIZE);
    salida.rdbuf()->pubsetbuf(this->bufferSalida,PPMC_BUFFER_SIZE);

    this->entrada=&entrada;
    this->salida=&salida;
}

void PPMC::comprimir(std::istream &entrada,std::ostream &salida)
{
    char caracter;
    Simbolo simbolo;
    
    this->inicializarEntradaSalida(entrada,salida);

    entrada.read(&caracter,sizeof(char));

    while (!entrada.eof())
    {
        simbolo = caracter;
        this->comprimirSimbolo(simbolo);
        entrada.read(&caracter,sizeof(char));
    }
    
    this->comprimirSimbolo(SimboloEOF);
}

void PPMC::descomprimir(std::istream &entrada,std::ostream &salida)
{
    char caracter;
    Simbolo simbolo;
    
    this->inicializarEntradaSalida(entrada,salida);
    
    simbolo=this->descomprimirSimbolo();

    while (simbolo!=SimboloEOF)
    {
        caracter = simbolo;
        salida.write(&caracter,sizeof(char));
        simbolo=this->descomprimirSimbolo();
    }
}

PPMC *PPMC::getNewInstance(Contexto::Orden orden,
	ICompresor *compresorAritmetico,
	Modelo *modeloCero)
{
	PPMC::deleteInstance();

	PPMC::instance=new PPMC(orden,compresorAritmetico,modeloCero);

	return PPMC::instance;
}

PPMC *PPMC::getInstance()
{
	return PPMC::instance;
}

/**
 * Libera recursos utilizados por la clase.
 */
void PPMC::deleteInstance()
{
	if (PPMC::instance!=NULL)
	{
		delete PPMC::instance;
		PPMC::instance=NULL;
	}
}
